package graphics;


import data.Map;
import data.Player;
import data.Tile;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**
 * @author Brett Garcia
 * @version 1.0
 */
public class MULEPanel extends JPanel {
	private final Map map;
	private final OuterPanel outerPanel;
	private boolean landSelection;
	private boolean playerDone;
	private boolean setup;
	private int time;
	private int roundCounter;
	private Tile currTile;
	private Player currPlayer;
	boolean inTown = false;
	private int playerLocation = 0;
	private Timer timer;
	private boolean inStore = false;
	private JPanel self = this;

	public MULEPanel(final Map map, OuterPanel panel, boolean isLoaded) {
		outerPanel = panel;
		setLayout(new BorderLayout());
		setSize(800, 600);
		this.map = map;
		roundCounter = 0;
		setup = !isLoaded;
		currTile = map.getMap()[0][0];
		currPlayer = map.getPlayers()[0];
		setFocusable(true);
		requestFocusInWindow();
		showMap(map.getCurrentPlayer());

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(final MouseEvent e) {
				requestFocusInWindow();
			}
		});

		//sets focus to MULEPanel to key presses will work
		vClicker();


		addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(final KeyEvent e) {

			}

			@Override
			public void keyPressed(final KeyEvent e) {
				if (!setup) {
					switch (e.getKeyCode()) {
						case KeyEvent.VK_UP:
							movePlayer(false, false, true, findPlayer(map.getCurrentPlayer()));
							break;
						case KeyEvent.VK_LEFT:
							movePlayer(true, false, false, findPlayer(map.getCurrentPlayer()));
							break;
						case KeyEvent.VK_DOWN:
							movePlayer(false, true, true, findPlayer(map.getCurrentPlayer()));
							break;
						case KeyEvent.VK_RIGHT:
							movePlayer(false, false, false, findPlayer(map.getCurrentPlayer()));
							break;
						case KeyEvent.VK_ENTER:
							map.setMuleOnTile(map.getCurrentPlayer().getFreeMule());
							repaint();
							break;

					}
				}
			}

			@Override
			public void keyReleased(final KeyEvent keyEvent) {

			}
		});
		repaint();
	}

	/**
	 * Starts new thread for land selection on the first run
	 * Otherwise it starts the player's turn to run for the appropriate time
	 * Then it moves to the next turn
	 *
	 * @param myTurn The player whose turn it is
	 */
	private void showMap(final Player myTurn) {
		removeAll();

		if (setup) {
			Thread thread = new Thread() {
				public void run() {
					landSelection();
				}
			};
			outerPanel.timerSwitch(false);
			thread.start();

		} else {
			time = map.getTurnLength(myTurn);
			timer = new Timer(time, new ActionListener() {
				@Override
				public void actionPerformed(final ActionEvent actionEvent) {
					outerPanel.timerSwitch(false);
					if (inStore) {
						map.getStore().removeStore(self);
					}
					inTown = false;
					map.nextTurn();
					setUpNewTimer();
					outerPanel.resetTime(time);
					repaint();
				}
			});
			timer.setRepeats(false);
			map.getMap()[0][0].setOnTile(map.getCurrentPlayer());
		}
		repaint();
	}

	/**
	 * Sets the entrance of the town
	 * in the center of the map
	 */
	private void enterTown() {
		inTown = true;
		playerLocation = 8;
		repaint();
	}

	/**
	 * Stops the current turn when the time runs out
	 * moves everything to the next turn
	 * starts countdown for the next players turn
	 */
	private void setUpNewTimer() {
		timer.stop();
		outerPanel.timerSwitch(false);

		time = map.getTurnLength(map.getCurrentPlayer());
		timer = new Timer(time, new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent actionEvent) {
				outerPanel.timerSwitch(false);
				if (inStore) {
					map.getStore().removeStore(self);
				}
				inTown = false;
				map.nextTurn();
				setUpNewTimer();

				repaint();
			}
		});
		timer.setRepeats(false);
		outerPanel.resetTime(time);
	}

	/**
	 * Draws the visual player representation whether he is in the tile, town or game setup
	 *
	 * @param g The graphics element used to do the drawing
	 */
	@Override
	protected void paintComponent(final Graphics g) {
		super.paintComponent(g);
		if (inStore) {
			map.getStore().drawStore(this);
			repaint();
			return;
		}
		if (inTown) {
			map.getTown().draw(g, getWidth(), getHeight(), playerLocation);
			vClicker();                  //sets focus to MULEPanel to key presses will work
			return;
		}
		for (Tile[] i : map.getMap()) {
			for (Tile j : i) {
				j.draw(g, getWidth() / 9, getHeight() / 5, setup, map.getCurrentPlayer());
			}
			vClicker();         //sets focus to MULEPanel to key presses will work
		}
		if (setup) {
			currTile.draw(g, getWidth() / 9, getHeight() / 5);
		}

	}

	/**
	 * moves a focus through each tile in the game
	 * When a player presses enter, that player has purchased the selected tile
	 * after one player selects a tile, the next one can buy a tile
	 * repeats until players run out of money or choose not to buy any tiles
	 */
	private void landSelection() {

		requestFocusInWindow();
		setFocusable(true);
		Player[] players = map.getPlayers();
		Tile[][] tiles = map.getMap();
		landSelection = true;
		playerDone = false;
		roundCounter = 0;
		currTile = tiles[0][0];

		class Selector extends AbstractAction {
			public void actionPerformed(final ActionEvent event) {

				if (roundCounter >= 2) {

					if (currPlayer.makePurchase(200)) {
						currPlayer.addProperty(currTile);
						landSelection = true;
						playerDone = true;
					} else {
						JOptionPane.showMessageDialog(MULEPanel.this, "Not enough money to buy tile.");
					}


				} else {
					currPlayer.addProperty(currTile);
					landSelection = true;
					playerDone = true;
				}
			}
		}

		class Skipper extends AbstractAction {
			public void actionPerformed(final ActionEvent event) {

				playerDone = true;
			}
		}


		getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ENTER"), "select");
		getActionMap().put("select", new Selector());

		getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ESCAPE"), "exit");
		getActionMap().put("exit", new Skipper());

		while (landSelection) {
			landSelection = false;

			for (Player p : players) {

				currPlayer = p;
				outerPanel.setPlayer(currPlayer);
				playerDone = false;

				for (Tile[] i : tiles) {
					for (final Tile j : i) {
						currTile = j;

						if (j.getOwner() == null && !j.isTown()) {
							repaint();

							try {
								Thread.sleep(2500);
							} catch (InterruptedException e) {
								System.err.println("Something went wrong with tile Selection pause");
							}

							if (playerDone) {
								break;
							}
						}
					}

					if (playerDone) {
						break;
					}
				}
			}
			roundCounter++;
		}
		setup = false;
		getActionMap().remove("select");
		getActionMap().remove("exit");
		showMap(map.getCurrentPlayer());
	}


	/**
	 * Finds which tile the player is on
	 *
	 * @param player The player being looked for
	 * @return The point on the map where the player is
	 */
	private Point findPlayer(final Player player) {
		for (int i = 0; i < map.getMap().length; i++) {
			for (int j = 0; j < map.getMap()[i].length; j++) {
				if (map.getMap()[i][j].getOnTile() == player) {
					return new Point(j, i);
				}
			}
		}
		System.err.printf("The player %s does not have a location.  Fix that", player);
		System.exit(1);
		return null; // won't actually be called
	}

	/**
	 * Moves the player in the desired direction
	 *
	 * @param isLeft     if it is to the left (false means right)
	 * @param isDown     if it is down (false means up)
	 * @param isVertical if it is vertical movement
	 * @param location   the current location of the player
	 */
	private void movePlayer(final boolean isLeft, final boolean isDown
			, final boolean isVertical, final Point location) {
		if (inTown && !inStore) {
			if (isVertical) {
				if (isDown) {
					if (playerLocation % 2 == 0) {
						return;
					} else {
						playerLocation++;
					}
				} else {
					if (playerLocation % 2 != 0) {
						return;
					} else {
						playerLocation--;
					}
				}
			} else {
				if (isLeft) {
					if (playerLocation > 2) {
						playerLocation -= 2;
					} else {
						return;
					}
				} else {
					if (playerLocation < 7) {
						playerLocation += 2;
					} else {
						return;
					}
				}
				repaint();
			}
			// check if on a building/or exit
			if (playerLocation % 2 != 0) {
				switch (playerLocation) {
					case 7:
						if (JOptionPane.showConfirmDialog(this, "Are you sure you want to exit the town?") == 0) {
							inTown = false;
							repaint();
						}
						break;
					case 5:
						if (JOptionPane.showConfirmDialog(this, "Would you like to gamble?") == 0) {
							outerPanel.timerSwitch(false);
							map.gamble(outerPanel.getTimeLeft());
							setUpNewTimer();
							inTown = false;
							repaint();
						}
						break;
					case 3:
						if (JOptionPane.showConfirmDialog(this, "Would you like to purchase property?") == 0) {
							enterLandOffice();
						}
						break;
					case 1:
						enterStore();
						repaint();
						break;
				}
			}
			return;
		}

		if (!outerPanel.getTimer()) {
			timer.start();
			outerPanel.timerSwitch(true);  //if the timer isn't running when someone moves, start it.
		}

		Point newPoint = new Point();
		if (isVertical) {
			if (isDown) {
				newPoint.y = location.y + 1;
			} else {
				newPoint.y = location.y - 1;
			}
			newPoint.x = location.x;
		} else {
			if (isLeft) {
				newPoint.x = location.x - 1;
			} else {
				newPoint.x = location.x + 1;
			}
			newPoint.y = location.y;
		}

		if (newPoint.x >= 0 && newPoint.y >= 0 && newPoint.x < 9 && newPoint.y < 5) {
			map.getMap()[location.y][location.x].setOnTile(null);
			map.getMap()[newPoint.y][newPoint.x].setOnTile(map.getCurrentPlayer());
			if (map.getMap()[newPoint.y][newPoint.x].isTown() && !setup) {
				enterTown();
			}
		}
		repaint();
	}

	@Override
	public void remove(final Component comp) {
		super.remove(comp);
		inStore = false;
		movePlayer(false, true, true, findPlayer(map.getCurrentPlayer()));
		//moves player down after exiting the store, since this shouldn't be called elsewhere
	}

	private void enterStore() {
		inStore = true;
	}

	private void enterLandOffice() {
	}

	public Player getCurrPlayer() {
		return map.getCurrentPlayer();
	}

	private void vClicker() {
		//sets focus to MULEPanel to key presses will work
		try {
			Robot clicker = new Robot();

			clicker.mousePress(InputEvent.BUTTON1_MASK);
			clicker.mouseRelease(InputEvent.BUTTON1_MASK);
		} catch (AWTException e) {
			System.err.print("fake mouse clicks aint workin' yo");
		}
	}
}
